/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using QName = System.Qizx.Api.QName;
using XQueryContext = System.Qizx.Api.XQueryContext;
using NamespaceContext = System.Qizx.Util.NamespaceContext;
using IQName = System.Qizx.Xdm.IQName;
using ModuleContext = System.Qizx.XQuery.ModuleContext;
using XQItemType = System.Qizx.XQuery.XQItemType;
using XQType = System.Qizx.XQuery.XQType;
using EmptyType = System.Qizx.XQuery.Dt.EmptyType;
using WrappedObjectType = System.Qizx.XQuery.Dt.WrappedObjectType;
using Expression = System.Qizx.XQuery.Op.Expression;
namespace System.Qizx.XQuery.Fn {

    /// <summary> Description of the prototype of an operator or a function.</summary>
    public class Prototype : Expression
        // (only for locating Signature)
    {
        public System.Qizx.Api.QName qname;

        public XQType returnType;
        public XQType declaredReturnType;

        public System.Qizx.Api.QName[] argNames = new System.Qizx.Api.QName[3];
        public XQType[] argTypes = new XQType[3];
        public int argCnt = 0;

        public bool vararg = false; // allows more than argCnt arguments
        public bool hidden_Renamed_Field = false;

        public static int NO_MATCH = 1000;



        public Prototype(System.Qizx.Api.QName qname, XQType returnType, System.Type classe, bool vararg) {
            this.qname = qname;
            this.returnType = returnType;
            this.implem = classe;
            this.vararg = vararg;
        }

        public Prototype(System.Qizx.Api.QName qname, XQType returnType, System.Type classe)
            : this(qname, returnType, classe, false) {
        }

        public static Prototype op(string name, XQType returnType, System.Type classe) {
            return new Prototype(IQName.GetQName(NamespaceContext.OP, name), returnType, classe);
        }

        public static Prototype fn(string name, XQType returnType, System.Type classe) {
            return new Prototype(IQName.GetQName(NamespaceContext.FN, name), returnType, classe);
        }

        public static Prototype varfn(string name, XQType returnType, System.Type classe) {
            return new Prototype(IQName.GetQName(NamespaceContext.FN, name), returnType, classe, true);
        }

        public static Prototype xs(string name, XQType returnType, System.Type classe) {
            return new Prototype(IQName.GetQName(NamespaceContext.XSD, name), returnType, classe);
        }

        public virtual Prototype hidden() {
            this.hidden_Renamed_Field = true;
            return this;
        }

        public virtual Prototype arg(string name, XQType type) {
            return arg(IQName.GetQName(name), type);
        }

        public virtual Prototype arg(System.Qizx.Api.QName name, XQType type) {
            if (argCnt >= argNames.Length) {
                QName[] oldn = argNames;
                argNames = new QName[argNames.Length + 3];
                Array.Copy(oldn, 0, argNames, 0, oldn.Length);
                XQType[] oldt = argTypes;
                argTypes = new XQType[argTypes.Length + 3];
                Array.Copy(oldt, 0, argTypes, 0, oldt.Length);
            }
            argNames[argCnt] = name;
            argTypes[argCnt] = type;
            ++argCnt;
            return this;
        }

        // redefined in JavaFunction
        public virtual Function.Call newInstance(XQueryContext ctx, Expression[] actualArguments) {
            try {
                Function.Call fc = (Function.Call)System.Activator.CreateInstance(this.implem);
                fc.prototype = this;
                fc.args = actualArguments;
                return fc;
            } catch (System.Exception e) {
                SupportClass.WriteStackTrace(e, Console.Error);
                return null;
            }
        }

        public virtual string getArgName(int arg, ModuleContext ctx) {
            return ctx.PrefixedName(argNames[arg]);
        }

        public virtual string displayName(QName name, NamespaceContext ctx) {
            if (ctx != null)
                return ctx.prefixedName(name);
            else
                return name.Name;
        }

        public virtual string displayName(NamespaceContext ctx) {
            if ((System.Object)NamespaceContext.OP == (System.Object)qname.Namespace)
                return "operator " + qname.Name;
            else
                return displayName(qname, ctx);
        }

        public virtual string toString(NamespaceContext ctx) {
            System.Text.StringBuilder buf = new System.Text.StringBuilder();
            buf.Append(displayName(ctx));
            buf.Append(" ( ");
            for (int a = 0; a < argCnt; a++) {
                if (a > 0)
                    buf.Append(", ");
                buf.Append('$');
                buf.Append(displayName(argNames[a], ctx));
                buf.Append(" as ").Append(argTypes[a].ToString());
            }
            if (vararg)
                buf.Append("...");
            return buf.Append(" ) as ").Append(returnType.ToString()).ToString();
        }

        // dynamic invocation: redefinable
        public XQValue invoke(Expression[] args, Expression call,
                              EvalContext lexicalContext,
                              Focus focus, EvalContext context) {
            if (!accepts(args.Length))
                context.error("XPTY0004", call, "invalid number of arguments");
            // needs to create a call, no way around
            Function.Call fc = instanciate(args);
            fc.module = call.module;
            fc.offset = call.offset;
            return fc.eval(null, context);
        }

        // redefined in JavaFunction and UserFunction
        public Function.Call instanciate(Expression[] actualArguments) {
            try {
                Function.Call fc = (Function.Call)Activator.CreateInstance(implem);
                fc.prototype = this;
                fc.args = actualArguments;
                return fc;
            } catch (Exception e) {
                System.Console.WriteLine(e);
                return null;
            }
        }

        // actual builtin functions, or UserFunction.Call or JavaFunction.Call
        public Type implem;

        public bool accepts(int argCount) {
            return argCnt == argCount || (vararg && argCount >= argCnt);
        }

        /// <summary> Tests an argument list for the corresponding formal argument types.
        /// Raises no error. 
        /// </summary>
        public virtual bool checkArgCount(Expression[] actualArguments) {
            if (actualArguments.Length != argCnt && !(vararg && actualArguments.Length >= argCnt))
                return false;
            // vararg extra arguments not checked

            // DISABLED to comply with XQ standard
            // for(int a = 0; a < argCnt; a++)
            // if(!checkArg( a, actualArguments[a] ))
            // return false;
            return true;
        }

        /// <summary> Computes a "distance" cumulating type distances of arguments</summary>
        public virtual int matchingDistance(Expression[] actualArguments) {
            // need to have the same 
            if (actualArguments.Length != argCnt && !(vararg && actualArguments.Length >= argCnt))
                return NO_MATCH;
            // sum matching distances of arguments
            int dist = 0;
            for (int i = 0; i < argCnt; i++) {
                XQType actualType = actualArguments[i].getType();
                dist += typeDistance(actualType, argTypes[i]);
            }

            return dist;
        }

        private int typeDistance(XQType actualType, XQType declaredType) {
            if (actualType == null || declaredType == null) {
                // abnormal
                System.Console.Error.WriteLine("OOPS Type matching: " + actualType + " vs " + declaredType);
                return NO_MATCH;
            }
            XQItemType acType = actualType.itemType();
            XQItemType decType = declaredType.itemType();
            if (acType == decType)
                return 0; // perfect match, dont care about occurrence factor
            if (acType.IsSubTypeOf(decType))
                return 1; // nearly perfect match

            switch (acType.quickCode()) {

                case XQType.QT_INT:
                case XQType.QT_DEC:
                    if (decType.IsSubTypeOf(XQItemType.DECIMAL) || decType == XQItemType.FLOAT || decType == XQItemType.DOUBLE)
                        return 2; // good match
                    return NO_MATCH;


                case XQType.QT_DOUBLE:
                case XQType.QT_FLOAT:
                    if (decType == XQItemType.FLOAT || decType == XQItemType.DOUBLE)
                        return 1; // good match
                    if (decType.IsSubTypeOf(XQItemType.DECIMAL))
                        return 2;
                    return stringMatch(decType);


                case XQType.QT_DTDUR:
                case XQType.QT_YMDUR:
                case XQType.QT_DUR:
                    if (decType.IsSubTypeOf(XQItemType.DURATION))
                        return 1;
                    return stringMatch(decType);


                case XQType.QT_DATE:
                case XQType.QT_DATETIME:
                case XQType.QT_TIME:
                    if (decType.IsSubTypeOf(XQItemType.DATE_TIME))
                        return 1;
                    return stringMatch(decType);


                case XQType.QT_STRING:
                case XQType.QT_ANYURI:
                    return stringMatch(decType);


                case XQType.QT_UNTYPED:
                    return decType.IsSubTypeOf(XQItemType.STRING) ? 1 : NO_MATCH;


                default:
                    if (decType == XQItemType.ITEM || acType == XQItemType.ITEM || acType is EmptyType)
                        return 1; // leave it to runtime
                    if (acType is WrappedObjectType && decType is WrappedObjectType) {
                        // consider wrapped Java types:
                        System.Type aClass = ((WrappedObjectType)acType).WrappedClass;
                        System.Type dClass = ((WrappedObjectType)decType).WrappedClass;
                        if (aClass == dClass)
                            return 0;
                        if (dClass != null && dClass.IsAssignableFrom(aClass))
                            return 1;
                        // else fail
                    }
                    return NO_MATCH;

            }
        }

        private int stringMatch(XQItemType decType) {
            return decType.IsSubTypeOf(XQItemType.STRING) ? 2 : NO_MATCH;
        }

        public override Expression Child(int rank) {
            return null;
        }
    }
}